package com.ershijin.config;

import com.ershijin.my.MyJdbcOAuth2AuthorizationService;
import com.ershijin.my.UserDetailsExpand;
import com.ershijin.util.KeyUtil;
import com.ershijin.util.OAuth2ConfigurerUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.*;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PasswordAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.web.authentication.*;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.util.ObjectUtils;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.Map;
import java.util.UUID;

@Configuration
public class AuthorizationServerConfig {
    @Value("${jks.public-key}")
    private String publicKeyString;

    @Value("${jks.private-key}")
    private String privateKeyString;

    @Value("${jks.key-id}")
    private String keyId;



    /**
     * 个性化 JWT Token
     * 自定义 OAuth2TokenContext 中的 OAuth 2.0 Token 属性
     */
    private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = (context) -> {
        Long uid = null;
        try {
            uid = (Long) ((Map)context.getAuthorization().getAccessToken().getMetadata().get("metadata.token.claims")).get("uid");
        } catch (Exception e) {}
        if (ObjectUtils.isEmpty(uid)) {
            UserDetailsExpand user = (UserDetailsExpand) context.getPrincipal().getPrincipal();
            uid = user.getId();
        }

        context.getClaims().claim("uid", uid);

        context.getClaims().claims(claims -> {
//            claims.remove("iss");
//            claims.remove("iat");
            claims.remove("nbf");
        });

//        context.getClaims().claim("authorities", userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).toArray());
//        context.getHeaders().header("client-id", context.getRegisteredClient().getClientId());

    };


    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        // 注入自定义 Token 对象
        http.setSharedObject(OAuth2TokenCustomizer.class, jwtCustomizer);

        OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = http.getConfigurer(OAuth2AuthorizationServerConfigurer.class);

        authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> tokenEndpoint.accessTokenRequestConverter(
                        new DelegatingAuthenticationConverter(Arrays.asList(
                                new OAuth2AuthorizationCodeAuthenticationConverter(),
                                new OAuth2RefreshTokenAuthenticationConverter(),
                                new OAuth2ClientCredentialsAuthenticationConverter(),
                                new OAuth2PasswordAuthenticationConverter())) // 自定义的 password 认证类型转换器
                ));


        SecurityFilterChain securityFilterChain = http.formLogin(Customizer.withDefaults()).build();

        OAuth2PasswordAuthenticationProvider oAuth2PasswordAuthenticationProvider = new OAuth2PasswordAuthenticationProvider(
                OAuth2ConfigurerUtils.getAuthorizationService(http),
                OAuth2ConfigurerUtils.getJwtEncoder(http),
                http.getSharedObject(AuthenticationManager.class),
                providerSettings()
        );
        oAuth2PasswordAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
        http.authenticationProvider(oAuth2PasswordAuthenticationProvider);

        return securityFilterChain;
    }


    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("articles-client")
                .clientSecret("{noop}secret")
//          .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethods(consumer -> {
                    consumer.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
                    consumer.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
                })
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) // 授权码方式
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.PASSWORD) // 密码模式
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) // 凭证式
                .authorizationGrantType(AuthorizationGrantType.IMPLICIT) // 隐藏式
                .redirectUri("http://app1:8080/login/oauth2/code/articles-client-oidc")
                .redirectUri("http://app1:8080/authorized")
                .scope(OidcScopes.OPENID)
                .scope("articles.read")
                .build();

        RegisteredClient registeredClient2 = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("articles-client2")
                .clientSecret("{noop}secret2")
//          .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethods(consumer -> {
                    consumer.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
                    consumer.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
                })
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) // 授权码方式
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.PASSWORD) // 密码模式
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) // 凭证式
                .authorizationGrantType(AuthorizationGrantType.IMPLICIT) // 隐藏式
                .redirectUri("http://app2:8081/login/oauth2/code/articles-client-oidc")
                .redirectUri("http://app2:8081/authorized")
                .scope(OidcScopes.OPENID)
                .scope("articles.read")
                .build();

        JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        if (null == jdbcRegisteredClientRepository.findByClientId("articles-client")) {
            jdbcRegisteredClientRepository.save(registeredClient);
        }
        if (null == jdbcRegisteredClientRepository.findByClientId("articles-client2")) {
            jdbcRegisteredClientRepository.save(registeredClient2);
        }

        return jdbcRegisteredClientRepository;
    }

    /**
     * 保存授权信息，授权服务器给我们颁发来token，那我们肯定需要保存吧，由这个服务来保存
     */
    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        MyJdbcOAuth2AuthorizationService authorizationService = new MyJdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);

        class CustomOAuth2AuthorizationRowMapper extends MyJdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper {
            public CustomOAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) {
                super(registeredClientRepository);
                getObjectMapper().configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
                getObjectMapper().enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
                this.setLobHandler(new DefaultLobHandler());
            }
        }
        class CustomOAuth2AuthorizationParametersMapper extends MyJdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper {
            public CustomOAuth2AuthorizationParametersMapper() {
                super();
                getObjectMapper().enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
            }
        }

        CustomOAuth2AuthorizationRowMapper oAuth2AuthorizationRowMapper =
                new CustomOAuth2AuthorizationRowMapper(registeredClientRepository);

        authorizationService.setAuthorizationRowMapper(oAuth2AuthorizationRowMapper);
        authorizationService.setAuthorizationParametersMapper(new CustomOAuth2AuthorizationParametersMapper());
        return authorizationService;
    }

    /**
     * 如果是授权码的流程，可能客户端申请了多个权限，比如：获取用户信息，修改用户信息，此Service处理的是用户给这个客户端哪些权限，比如只给获取用户信息的权限
     */
    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }

    @Bean
    @SneakyThrows
    public JWKSource<SecurityContext> jwkSource() {

        RSAPublicKey publicKey = (RSAPublicKey) KeyUtil.getPublicKey(publicKeyString);
        RSAPrivateKey privateKey = (RSAPrivateKey) KeyUtil.getPrivateKey(privateKeyString);

        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(keyId)
                .build();


        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }

    /**
     * 配置个端点的路径，如获取token、授权等
     * @return
     */
    @Bean
    public ProviderSettings providerSettings() {
        return ProviderSettings.builder()
                .tokenEndpoint("/oauth2/token")
//                .issuer("http://auth-server:9000")
                .build();
    }
}